﻿using System;

namespace Nomo.CoreModule
{
    /// <summary>
    /// 提供一些数学方法。
    /// </summary>
    public static unsafe class NomoMath
    {
        /// <summary>
        /// 计算两个双精度浮点数的商，并通过输出参数返回余数。
        /// </summary>
        /// <param name="a">被除数。</param>
        /// <param name="b">除数。</param>
        /// <param name="result">余数。</param>
        /// <returns>指定数字的商。</returns>
        public static double DivRem(double a, double b, out double result)
        {
            return (a - (result = a % b)) / b;
        }

        /// <summary>
        /// 返回 <paramref name="a"/> 和 <paramref name="b"/> 的最大公约数。
        /// </summary>
        /// <param name="a">一个值。</param>
        /// <param name="b">另一个值。</param>
        /// <returns><paramref name="a"/> 和 <paramref name="b"/> 的最大公约数。</returns>
        /// <exception cref="ArithmeticException"><paramref name="a"/> 或 <paramref name="b"/> 为 0 或为负。</exception>
        public static int Gcd(int a, int b)
        {
            if (a <= 0 || b <= 0)
            {
                throw new ArithmeticException();
            }

            while (true)
            {
                var remainder = a % b;
                if (remainder == 0)
                {
                    return b;
                }
                a = b;
                b = remainder;
            }
        }

        /// <summary>
        /// 返回 <paramref name="a"/> 和 <paramref name="b"/> 的最小公倍数。
        /// </summary>
        /// <param name="a">一个值。</param>
        /// <param name="b">另一个值。</param>
        /// <returns><paramref name="a"/> 和 <paramref name="b"/> 的最小公倍数。</returns>
        /// <exception cref=" ArithmeticException"><paramref name="a"/> 或 <paramref name="b"/> 为 0 或为负。</exception>
        public static int Lcm(int a, int b)
        {
            if (a <= 0 || b <= 0)
            {
                throw new ArithmeticException();
            }

            return a * b / Gcd(a, b);
        }

        /// <summary>
        /// 返回等于或小于 <paramref name="value"/> 的最大的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或小于 <paramref name="value"/> 的最大的 2 的幂。</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> 为 0 .</exception>
        public static byte LastPow2(byte value)
        {
            if (value == 0)
            {
                throw new ArgumentOutOfRangeException();
            }
            value >>= 1;
            value =   (byte) (value | value >> 1);
            value =   (byte) (value | value >> 2);
            value =   (byte) (value | value >> 4);
            return ++value;
        }

        /// <summary>
        /// 返回等于或小于 <paramref name="value"/> 的最大的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或小于 <paramref name="value"/> 的最大的 2 的幂。</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> 小于或等于 0 .</exception>
        public static sbyte LastPow2(sbyte value)
        {
            return value < 0 ? throw new ArgumentOutOfRangeException() : (sbyte) LastPow2((byte) value);
        }

        /// <summary>
        /// 返回等于或小于 <paramref name="value"/> 的最大的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或小于 <paramref name="value"/> 的最大的 2 的幂。</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> 为 0 .</exception>
        public static ushort LastPow2(ushort value)
        {
            if (value == 0)
            {
                throw new ArgumentOutOfRangeException();
            }
            value >>= 1;
            value =   (ushort) (value | value >> 1);
            value =   (ushort) (value | value >> 2);
            value =   (ushort) (value | value >> 4);
            value =   (ushort) (value | value >> 8);
            return ++value;
        }

        /// <summary>
        /// 返回等于或小于 <paramref name="value"/> 的最大的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或小于 <paramref name="value"/> 的最大的 2 的幂。</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> 小于或等于 0 .</exception>
        public static short LastPow2(short value)
        {
            return value < 0 ? throw new ArgumentOutOfRangeException() : (short) LastPow2((ushort) value);
        }

        /// <summary>
        /// 返回等于或小于 <paramref name="value"/> 的最大的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或小于 <paramref name="value"/> 的最大的 2 的幂。</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> 为 0 .</exception>
        public static uint LastPow2(uint value)
        {
            if (value == 0U)
            {
                throw new ArgumentOutOfRangeException();
            }
            value >>= 1;
            value |=  value >> 1;
            value |=  value >> 2;
            value |=  value >> 4;
            value |=  value >> 8;
            value |=  value >> 16;
            return ++value;
        }

        /// <summary>
        /// 返回等于或小于 <paramref name="value"/> 的最大的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或小于 <paramref name="value"/> 的最大的 2 的幂。</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> 小于或等于 0 .</exception>
        public static int LastPow2(int value)
        {
            return value < 0 ? throw new ArgumentOutOfRangeException() : (int) LastPow2((uint) value);
        }

        /// <summary>
        /// 返回等于或小于 <paramref name="value"/> 的最大的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或小于 <paramref name="value"/> 的最大的 2 的幂。</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> 为 0 .</exception>
        public static ulong LastPow2(ulong value)
        {
            if (value == 0UL)
            {
                throw new ArgumentOutOfRangeException();
            }
            value >>= 1;
            value |=  value >> 1;
            value |=  value >> 2;
            value |=  value >> 4;
            value |=  value >> 8;
            value |=  value >> 16;
            value |=  value >> 32;
            return ++value;
        }

        /// <summary>
        /// 返回等于或小于 <paramref name="value"/> 的最大的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或小于 <paramref name="value"/> 的最大的 2 的幂。</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> 小于或等于 0 .</exception>
        public static long LastPow2(long value)
        {
            return value < 0L ? throw new ArgumentOutOfRangeException() : (long) LastPow2((ulong) value);
        }

        /// <summary>
        /// 返回等于或大于 <paramref name="value"/> 的最小的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或大于 <paramref name="value"/> 的最小的 2 的幂。</returns>
        /// <exception cref="OverflowException">将要返回的值超过了返回值类型的最大值。</exception>
        public static byte NextPow2(byte value)
        {
            if (value == 0)
            {
                return 1;
            }
            --value;
            value = (byte) (value | value >> 1);
            value = (byte) (value | value >> 2);
            value = (byte) (value | value >> 4);
            return checked(++value);
        }

        /// <summary>
        /// 返回等于或大于 <paramref name="value"/> 的最小的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或大于 <paramref name="value"/> 的最小的 2 的幂。</returns>
        /// <exception cref="OverflowException">将要返回的值超过了返回值类型的最大值。</exception>
        public static sbyte NextPow2(sbyte value)
        {
            return value < 0 ? (sbyte) 1 : checked((sbyte) NextPow2((byte) value));
        }

        /// <summary>
        /// 返回等于或大于 <paramref name="value"/> 的最小的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或大于 <paramref name="value"/> 的最小的 2 的幂。</returns>
        /// <exception cref="OverflowException">将要返回的值超过了返回值类型的最大值。</exception>
        public static ushort NextPow2(ushort value)
        {
            if (value == 0)
            {
                return 1;
            }
            --value;
            value = (ushort) (value | value >> 1);
            value = (ushort) (value | value >> 2);
            value = (ushort) (value | value >> 4);
            value = (ushort) (value | value >> 8);
            return checked(++value);
        }

        /// <summary>
        /// 返回等于或大于 <paramref name="value"/> 的最小的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或大于 <paramref name="value"/> 的最小的 2 的幂。</returns>
        /// <exception cref="OverflowException">将要返回的值超过了返回值类型的最大值。</exception>
        public static short NextPow2(short value)
        {
            return value < 0 ? (short) 1 : checked((short) NextPow2((ushort) value));
        }

        /// <summary>
        /// 返回等于或大于 <paramref name="value"/> 的最小的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或大于 <paramref name="value"/> 的最小的 2 的幂。</returns>
        /// <exception cref="OverflowException">将要返回的值超过了返回值类型的最大值。</exception>
        public static uint NextPow2(uint value)
        {
            if (value == 0U)
            {
                return 1U;
            }
            --value;
            value |= value >> 1;
            value |= value >> 2;
            value |= value >> 4;
            value |= value >> 8;
            value |= value >> 16;
            return checked(++value);
        }

        /// <summary>
        /// 返回等于或大于 <paramref name="value"/> 的最小的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或大于 <paramref name="value"/> 的最小的 2 的幂。</returns>
        /// <exception cref="OverflowException">将要返回的值超过了返回值类型的最大值。</exception>
        public static int NextPow2(int value)
        {
            return value < 0 ? 1 : checked((int) NextPow2((uint) value));
        }

        /// <summary>
        /// 返回等于或大于 <paramref name="value"/> 的最小的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或大于 <paramref name="value"/> 的最小的 2 的幂。</returns>
        /// <exception cref="OverflowException">将要返回的值超过了返回值类型的最大值。</exception>
        public static ulong NextPow2(ulong value)
        {
            if (value == 0UL)
            {
                return 1UL;
            }
            --value;
            value |= value >> 1;
            value |= value >> 2;
            value |= value >> 4;
            value |= value >> 8;
            value |= value >> 16;
            value |= value >> 32;
            return checked(++value);
        }

        /// <summary>
        /// 返回等于或大于 <paramref name="value"/> 的最小的 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns>等于或大于 <paramref name="value"/> 的最小的 2 的幂。</returns>
        /// <exception cref="OverflowException">将要返回的值超过了返回值类型的最大值。</exception>
        public static long NextPow2(long value)
        {
            return value < 0L ? 1L : checked((long) NextPow2((ulong) value));
        }

        /// <summary>
        /// 返回一个值，这个值指示 <paramref name="value"/> 是否是 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns><paramref name="value"/> 是 2 的幂吗？</returns>
        public static bool IsPow2(ulong value)
        {
            return value != 0UL && (value & (value - 1UL)) == 0UL;
        }

        /// <summary>
        /// 返回一个值，这个值指示 <paramref name="value"/> 是否是 2 的幂。
        /// </summary>
        /// <param name="value">一个值。</param>
        /// <returns><paramref name="value"/> 是 2 的幂吗？</returns>
        public static bool IsPow2(long value)
        {
            return value != 0L && (value & (value - 1L)) == 0L;
        }

        /// <summary>
        /// 返回 <paramref name="value"/> 的 <paramref name="power"/> 次幂。
        /// </summary>
        /// <param name="value">底数。</param>
        /// <param name="power">指数。</param>
        /// <returns><paramref name="value"/> 的 <paramref name="power"/> 次幂。</returns>
        /// <exception cref="NotSupportedException"><paramref name="power"/> 为负。</exception>
        /// <exception cref="ArithmeticException"><paramref name="value"/> 和 <paramref name="power"/> 都为 0 .</exception>
        public static int ToThePowerOf(int value, int power)
        {
            if (power < 0)
            {
                throw new NotSupportedException();
            }

            if (value == 0)
            {
                if (power == 0)
                {
                    throw new ArithmeticException();
                }
                return 0;
            }

            if (power == 0)
            {
                return 1;
            }

            var product = 0;
            for (var i = 0; i < power; i++)
            {
                product *= value;
            }
            return product;
        }

        /// <summary>
        /// 样条插值。
        /// </summary>
        /// <param name="t">插值。</param>
        /// <param name="a">进行样条插值的多个数中的第 1 个数。</param>
        /// <param name="b">进行样条插值的多个数中的第 2 个数。</param>
        /// <returns>样条插值的结果。</returns>
        public static float Interpolation(float t, float a, float b)
        {
            return a + (b - a) * t;
        }

        /// <summary>
        /// 样条插值。
        /// </summary>
        /// <param name="t">插值。</param>
        /// <param name="a">进行样条插值的多个数中的第 1 个数。</param>
        /// <param name="b">进行样条插值的多个数中的第 2 个数。</param>
        /// <param name="c">进行样条插值的多个数中的第 3 个数。</param>
        /// <returns>样条插值的结果。</returns>
        public static float Interpolation(float t, float a, float b, float c)
        {
            var values = stackalloc float[3];
            values[0] = a;
            values[1] = b;
            values[2] = c;
            return InterpolationStackAllocInternal(values, t, 3);
        }

        /// <summary>
        /// 样条插值。
        /// </summary>
        /// <param name="t">插值。</param>
        /// <param name="a">进行样条插值的多个数中的第 1 个数。</param>
        /// <param name="b">进行样条插值的多个数中的第 2 个数。</param>
        /// <param name="c">进行样条插值的多个数中的第 3 个数。</param>
        /// <param name="d">进行样条插值的多个数中的第 4 个数。</param>
        /// <returns>样条插值的结果。</returns>
        public static float Interpolation(float t, float a, float b, float c, float d)
        {
            var values = stackalloc float[4];
            values[0] = a;
            values[1] = b;
            values[2] = c;
            values[3] = d;
            return InterpolationStackAllocInternal(values, t, 4);
        }

        /// <summary>
        /// 样条插值。
        /// </summary>
        /// <param name="t">插值。</param>
        /// <param name="values">进行样条插值的多个数。</param>
        /// <returns>样条插值的结果。</returns>
        public static float Interpolation(float t, params float[] values)
        {
            var count = values.Length;
            switch (count)
            {
                case 0:
                    return default;
                case 1:
                    return values[0];
                case 2:
                    return Interpolation(t, values[0], values[1]);
                default:
                    fixed (float* ptr = values)
                    {
                        return InterpolationInternal(ptr, t, count);
                    }
            }
        }

        private const int MaxStackAllocationAllowed       = 2 << 9;
        private const int MaxStackFloatArrayLengthAllowed = MaxStackAllocationAllowed / sizeof(float);

        private static float InterpolationInternal(float* points, float t, int count)
        {
            return count <= MaxStackFloatArrayLengthAllowed ? InterpolationStackAllocInternal(points, t, count) : InterpolationHeapAllocInternal(points, t, count);
        }

        private static float InterpolationStackAllocInternal(float* values, float t, int count)
        {
            var array = stackalloc float[count - 1];
            for (var i = 0; i < count - 1; i++)
            {
                array[i] = Interpolation(t, values[i], values[i + 1]);
            }
            while (--count > 0)
            {
                for (var i = 0; i < count - 1; i++)
                {
                    array[i] = Interpolation(t, array[i], array[i + 1]);
                }
            }
            return *array;
        }

        private static float InterpolationHeapAllocInternal(float* values, float t, int count)
        {
            var heapArray = new float[count - 1];
            fixed (float* array = heapArray)
            {
                for (var i = 0; i < count - 1; i++)
                {
                    array[i] = Interpolation(t, values[i], values[i + 1]);
                }
                while (--count > 0)
                {
                    for (var i = 0; i < count - 1; i++)
                    {
                        array[i] = Interpolation(t, array[i], array[i + 1]);
                    }
                }
                return *array;
            }
        }

        /// <summary>
        /// 返回两个数中的较小者。
        /// </summary>
        /// <param name="a">第 1 个数。</param>
        /// <param name="b">第 2 个数。</param>
        /// <returns>两个数中的较小者。</returns>
        public static float Min(float a, float b)
        {
            return a < b ? a : b;
        }

        /// <summary>
        /// 返回多个数中的最小者。
        /// </summary>
        /// <param name="values">多个数。</param>
        /// <returns>多个数中的最小者。</returns>
        public static float Min(params float[] values)
        {
            var result     = default(float);
            var hasElement = false;
            foreach (var value in values)
            {
                if (!hasElement)
                {
                    hasElement = true;
                    result     = value;
                    continue;
                }
                if (result > value)
                {
                    result = value;
                }
            }
            return result;
        }

        /// <summary>
        /// 返回两个数中的较大者。
        /// </summary>
        /// <param name="a">第 1 个数。</param>
        /// <param name="b">第 2 个数。</param>
        /// <returns>两个数中的较大者。</returns>
        public static float Max(float a, float b)
        {
            return a > b ? a : b;
        }

        /// <summary>
        /// 将一个数的值钳制在两个数之间。
        /// </summary>
        /// <param name="value">被钳制的数。</param>
        /// <param name="a">第 1 个钳制值。</param>
        /// <param name="b">第 2 个钳制值。</param>
        /// <returns>钳制后的结果。</returns>
        public static float Clamp(float value, float a, float b)
        {
            return ClampMinMax(value, Min(a, b), Max(a, b));
        }

        private static float ClampMinMax(float value, float min, float max)
        {
            return value < min ? min : value > max ? max : value;
        }

        /// <summary>
        /// 将一个数的值钳制在 0 和 1 之间。
        /// </summary>
        /// <param name="value">被钳制的数。</param>
        /// <returns>钳制后的结果。</returns>
        public static float Clamp01(float value)
        {
            return ClampMinMax(value, 0F, 1F);
        }
    }
}
